﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Plugins;
using Xenta.Security;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a payment.
    /// </summary>
    public class CreatePayment : CreateAuditable<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the invoice identifier.
        /// </summary>
        public int InvoiceID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the payment processor global unique identifier.
        /// </summary>
        public Guid ProcessorID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the payment amount.
        /// </summary>
        public decimal Amount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the handling fee.
        /// </summary>
        public decimal HandlingFee
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public PaymentFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(Amount <= Decimal.Zero)
                return Err.Of<PaymentEntity>().NotValid("Amount");
            if(HandlingFee < Decimal.Zero)
                return Err.Of<PaymentEntity>().NotValid("HandlingFee");
            if(!uow.Infrastructure.CheckDependency<IPaymentProcessor>(ProcessorID))
                return Err.Of<IPaymentProcessor>().NotFound();

            var invoice = uow.Execute(new GetInvoice
            {
                ThrowsException = true,
                EntityID = InvoiceID
            }).Result;
            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve(ProcessorID)
                    as IPaymentProcessor;
                if(!processor.CanApplyTo(invoice))
                    return Err.Of(processor).NotAllowed();
            }

            return base.Validate(uow);
        }

        protected override void Construct(IUnitOfWork uow, PaymentEntity entity)
        {
            base.Construct(uow, entity);

            entity.Guid = Guid.NewGuid();
            entity.Customer = uow.Execute(new GetCustomer
            {
                ThrowsException = true,
                EntityID = CustomerID
            }).Result;
            entity.Invoice = uow.Execute(new GetInvoice
            {
                ThrowsException = true,
                EntityID = InvoiceID
            }).Result;
            entity.ProcessorID = ProcessorID;
            entity.Amount = Amount;
            entity.HandlingFee = HandlingFee;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the payment.
    /// </summary>
    public class UpdatePayment : UpdateAuditable<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the payment amount.
        /// </summary>
        public decimal Amount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the handling fee.
        /// </summary>
        public decimal HandlingFee
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public PaymentFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(Amount <= Decimal.Zero)
                return Err.Of<PaymentEntity>().NotValid("Amount");
            if(HandlingFee < Decimal.Zero)
                return Err.Of<PaymentEntity>().NotValid("HandlingFee");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, PaymentEntity entity)
        {
            base.Revise(uow, entity);

            entity.Amount = Amount;
            entity.HandlingFee = HandlingFee;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the payment.
    /// </summary>
    public class DeletePayment : DeleteEntity<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion

        #region Utilities

        protected override void Destruct(IUnitOfWork uow, PaymentEntity entity)
        {
            foreach(var i in entity.Attributes)
            {
                uow.Execute(new DeletePaymentAttribute
                {
                    EntityID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the payment by identifier.
    /// </summary>
    public class GetPayment : GetEntity<PaymentEntity>,
        IApiOperation
    {
    }

    /// <summary>
    /// Gets the payment by guid.
    /// </summary>
    public class GetPaymentByGuid : QueryEntity<PaymentEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the account guid.
        /// </summary>
        public Guid Guid
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<PaymentEntity> Build(IUnitOfWork uow, IQueryable<PaymentEntity> query)
        {
            return base.Build(uow, query).Where(x => x.Guid == Guid);
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over payment collection.
    /// </summary>
    public class SearchPayments : SearchOperation<PaymentEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchPayments()
        {
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                PaymentSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the invoice identifier.
        /// </summary>
        public int? InvoiceID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int? CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public PaymentFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'from'.
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'to'.
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public PaymentSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<PaymentEntity> Build(IUnitOfWork uow, IQueryable<PaymentEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Customer)
                .ThenFetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByDateRange(x => x.CreatedOn, CreatedFrom, CreatedTo);
            if(InvoiceID.HasValue)
                query = query.Where(x => x.Invoice.EntityID == InvoiceID.Value);
            if(CustomerID.HasValue)
                query = query.Where(x => x.Customer.EntityID == CustomerID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case PaymentSortBy.Amount:
                            query = query.OrderBy(x => x.Amount, SortDir, i != 0);
                            break;
                        case PaymentSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Workflow__

    /// <summary>
    /// Places a payment
    /// </summary>
    public class PlacePayment : Operation<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        return customer.EntityID == CustomerID;
                    });
            }
        }

        /// <summary>
        /// Gets or sets the invoice identifier.
        /// </summary>
        public int InvoiceID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the invoice identifier.
        /// </summary>
        public int CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the payment processor global unique identifier.
        /// </summary>
        public Guid ProcessorID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the payment amount.
        /// </summary>
        public decimal Amount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var payment = uow.Execute(new CreatePayment
            {
                CustomerID = CustomerID,
                Amount = Amount,
                HandlingFee = Decimal.Zero,
                InvoiceID = InvoiceID,
                ProcessorID = ProcessorID,
                Flags = PaymentFlags.None
            }).Result;

            if(Attributes != null && Attributes.Any())
            {
                uow.Execute(new BulkUpdatePaymentAttributes
                {
                    HolderID = payment.EntityID,
                    Attributes = Attributes
                });
            }

            decimal handlingFee = Decimal.Zero;
            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve(payment.ProcessorID)
                    as IPaymentProcessor;
                handlingFee = processor.CalcHandlingFee(payment.Invoice, payment.Amount);
            }

            payment = uow.Execute(new UpdatePayment
            {
                EntityID = payment.EntityID,
                Amount = Amount,
                HandlingFee = handlingFee,
                Flags = payment.Flags
            }).Result;
            payment = uow.Execute(new SetPaymentStatus
            {
                EntityID = payment.EntityID,
                Status = PaymentFlags.Pending
            }).Result;

            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Authorizes a payment
    /// </summary>
    public class AuthorizePayment : OperateEntityByID<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var payment = uow.Execute(new GetPayment
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            if(Attributes != null && Attributes.Any())
            {
                uow.Execute(new BulkUpdatePaymentAttributes
                {
                    HolderID = payment.EntityID,
                    Attributes = Attributes
                });
            }
            payment = uow.Execute(new SetPaymentStatus
            {
                EntityID = EntityID,
                Status = PaymentFlags.Authorized
            }).Result;
            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Captures a payment
    /// </summary>
    public class CapturePayment : OperateEntityByID<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var payment = uow.Execute(new GetPayment
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            if(Attributes != null && Attributes.Any())
            {
                uow.Execute(new BulkUpdatePaymentAttributes
                {
                    HolderID = payment.EntityID,
                    Attributes = Attributes
                });
            }
            payment = uow.Execute(new SetPaymentStatus
            {
                EntityID = EntityID,
                Status = PaymentFlags.Captured
            }).Result;
            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Voids a payment
    /// </summary>
    public class VoidPayment : OperateEntityByID<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var payment = uow.Execute(new GetPayment
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            if(Attributes != null && Attributes.Any())
            {
                uow.Execute(new BulkUpdatePaymentAttributes
                {
                    HolderID = payment.EntityID,
                    Attributes = Attributes
                });
            }
            payment = uow.Execute(new SetPaymentStatus
            {
                EntityID = EntityID,
                Status = PaymentFlags.Voided
            }).Result;
            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Refunds a payment.
    /// </summary>
    public class RefundPayment : OperateEntityByID<PaymentEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var payment = uow.Execute(new GetPayment
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            if(Attributes != null && Attributes.Any())
            {
                uow.Execute(new BulkUpdatePaymentAttributes
                {
                    HolderID = payment.EntityID,
                    Attributes = Attributes
                });
            }
            payment = uow.Execute(new SetPaymentStatus
            {
                EntityID = EntityID,
                Status = PaymentFlags.Refunded
            }).Result;
            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Handles the instance payment notification.
    /// </summary>
    public class HandleInstantPaymentNotification : Operation<PaymentEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the IPN handler identifier.
        /// </summary>
        public Guid HandlerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(Attributes == null || !Attributes.Any())
                return Err.Of(this).No("Attributes");
            if(!uow.Infrastructure.CheckDependency<IIPNHandler>(HandlerID))
                return Err.Of<IIPNHandler>().NotFound();
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            var status = PaymentFlags.None;
            PaymentEntity payment = null;

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var handler = ioc.Resolve(HandlerID)
                    as IIPNHandler;
                var guid = Guid.Empty;

                string ret = handler.Identify(Attributes, out guid);
                if(!String.IsNullOrWhiteSpace(ret))
                    throw Err.Of(handler).Message(ret).Ex();

                payment = uow.Execute(new GetPaymentByGuid
                {
                    ThrowsException = true,
                    Guid = guid
                }).Result;

                ret = handler.Handle(Attributes, payment, out status);
                if(!String.IsNullOrWhiteSpace(ret))
                    throw Err.Of(handler).Message(ret).Ex();
            }

            switch(status)
            {
                case PaymentFlags.Voided:
                    payment = uow.Execute(new VoidPayment
                    {
                        EntityID = payment.EntityID,
                        Attributes = Attributes
                    }).Result;
                    break;
                case PaymentFlags.Refunded:
                    payment = uow.Execute(new RefundPayment
                    {
                        EntityID = payment.EntityID,
                        Attributes = Attributes
                    }).Result;
                    break;
                case PaymentFlags.Authorized:
                    payment = uow.Execute(new AuthorizePayment
                    {
                        EntityID = payment.EntityID,
                        Attributes = Attributes
                    }).Result;
                    break;
                case PaymentFlags.Captured:
                    payment = uow.Execute(new CapturePayment
                    {
                        EntityID = payment.EntityID,
                        Attributes = Attributes
                    }).Result;
                    break;
            }
            result = payment;
        }

        #endregion
    }

    /// <summary>
    /// Handles a payment.
    /// </summary>
    public class SetPaymentStatus : UpdateAuditable<PaymentEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the payment status.
        /// </summary>
        public PaymentFlags Status
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            var entity = uow.Execute(new GetPayment
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            var newStatus = FlagsHelper.G1_Flags(Status);
            var oldStatus = FlagsHelper.G1_Flags(entity.Flags);

            if(newStatus == oldStatus)
                return Err.Of(this).NotAllowed();
            switch(newStatus)
            {
                case PaymentFlags.Voided:
                    if(oldStatus != PaymentFlags.Authorized)
                        return Err.Of(this).NotAllowed();
                    break;
                case PaymentFlags.Refunded:
                    if(oldStatus != PaymentFlags.Captured)
                        return Err.Of(this).NotAllowed();
                    break;
                case PaymentFlags.Pending:
                    if(oldStatus != PaymentFlags.None)
                        return Err.Of(this).NotAllowed();
                    break;
                case PaymentFlags.Authorized:
                    if(oldStatus != PaymentFlags.Pending)
                        return Err.Of(this).NotAllowed();
                    break;
                case PaymentFlags.Captured:
                    if(oldStatus != PaymentFlags.Pending && oldStatus != PaymentFlags.Authorized)
                        return Err.Of(this).NotAllowed();
                    break;
                default:
                    return Err.Of(this).NotValid("Status");
            }
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out PaymentEntity result)
        {
            base.Work(uow, out result);

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var processor = ioc.Resolve(result.ProcessorID)
                    as IPaymentProcessor;
                IDictionary<String, String> hndAttributes = null;
                string comment = String.Empty;
                switch(FlagsHelper.G1_Flags(result.Flags))
                {
                    case PaymentFlags.Voided:
                        hndAttributes = processor.Void(result, out comment);
                        break;
                    case PaymentFlags.Pending:
                        hndAttributes = processor.Place(result, out comment);
                        break;
                    case PaymentFlags.Refunded:
                        hndAttributes = processor.Refund(result, out comment);
                        break;
                    case PaymentFlags.Authorized:
                        hndAttributes = processor.Authorize(result, out comment);
                        break;
                    case PaymentFlags.Captured:
                        hndAttributes = processor.Capture(result, out comment);
                        break;
                }
                if(hndAttributes != null && hndAttributes.Any())
                {
                    uow.Execute(new BulkUpdatePaymentAttributes
                    {
                        HolderID = result.EntityID,
                        Attributes = hndAttributes
                    });
                }

                (ioc.Resolve(result.Invoice.Order.SalesModelID) as ISalesModel)
                    .OnPaymentStatusChanged(uow, result);

                if(!String.IsNullOrWhiteSpace(comment))
                {
                    uow.Execute(new CreateOrderComment
                    {
                        HolderID = result.Invoice.Order.EntityID,
                        Text = comment,
                        Flags = OrderCommentFlags.Active
                    });
                }
            }
        }

        protected override void Revise(IUnitOfWork uow, PaymentEntity entity)
        {
            base.Revise(uow, entity);

            entity.Flags &= (~FlagsHelper.G1_Flags(entity.Flags));
            entity.Flags |= FlagsHelper.G1_Flags(Status);
            if(entity.Flags.HasFlag(PaymentFlags.Pending) ||
                entity.Flags.HasFlag(PaymentFlags.Authorized))
            {
                entity.Flags |= PaymentFlags.Active;
            }
            else
                entity.Flags &= (~PaymentFlags.Active);
        }

        #endregion
    }

    #endregion
}
